package com.xrui.hbaseview.actions;

import org.apache.commons.lang.time.StopWatch;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * This class represents a runnable action.
 */
public class RunnableAction<R> implements Runnable {

    //region Constants
    private final static Logger LOG = LoggerFactory.getLogger(RunnableAction.class);
    //endregion

    //region Variables
    private String name;
    private Action<R> action;
    private Thread thread;
    private boolean isRunning;
    private boolean interrupted;
    private R result;
    //endregion

    //region Constructor
    public RunnableAction(String name, Action<R> action) {
        this.name = name;
        this.action = action;
    }
    //endregion

    //region Public Methods
    public static <R> RunnableAction<R> run(String name, Action<R> action) {
        RunnableAction<R> runnableAction = new RunnableAction<>(name, action);
        runnableAction.start();

        return runnableAction;
    }

    public static <R> R runAndWait(String name, Action<R> action, long timeout) {
        RunnableAction<R> runnableAction = new RunnableAction<>(name, action);
        runnableAction.start();

        runnableAction.waitOrAbort(timeout);
        return runnableAction.result;
    }

    //region Public Properties
    public R getResult() {
        return result;
    }
    //endregion

    public boolean isCompleted() {
        return !isRunning && !interrupted;
    }

    public boolean isRunning() {
        return isRunning;
    }

    public void start() {
        if (this.thread == null) {
            this.thread = new Thread(this);
            this.thread.setName(this.name);
            this.thread.setDaemon(true);
            this.thread.start();

            this.isRunning = true;

            LOG.info("Action '{}' started.", this.name);
        }
    }

    public void abort() {
        if (this.isRunning) {
            this.interrupted = true;

            this.thread.interrupt();
            this.thread = null;
        }
    }

    public void waitUntil(long timeout) {
        StopWatch stopWatch = new StopWatch();
        stopWatch.start();

        long localTimeout = timeout / 10;

        while (this.isRunning) {
            try {
                Thread.sleep(localTimeout);

                if (stopWatch.getTime() > timeout) {
                    break;
                }
            } catch (InterruptedException ignore) {
            }
        }

        stopWatch.stop();
    }

    public void waitOrAbort(long timeout) {
        waitUntil(timeout);

        if (this.isRunning) {
            abort();
        }
    }

    @Override
    public void run() {
        try {
            this.result = action.run();
            this.isRunning = false;

            LOG.info("Action '{}' completed.", this.name);
        } catch (Exception e) {
            this.isRunning = false;

            if (this.interrupted) {
                LOG.info("Action '{}' aborted.", this.name);
            } else {
                LOG.info("Action '{}' failed.", this.name);
                this.action.onError(e);
            }
        }
    }
    //endregion
}
